package fr.pioug.gdocs.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.TimeZone;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;

import com.google.gdata.client.docs.DocsService;
import com.google.gdata.client.http.HttpAuthToken;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.TextConstruct;
import com.google.gdata.data.docs.DocumentListEntry;
import com.google.gdata.data.docs.DocumentListFeed;
import com.google.gdata.data.docs.FolderEntry;
import com.google.gdata.data.docs.DocumentListEntry.MediaType;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.InvalidEntryException;
import com.google.gdata.util.ServiceException;

import fr.pioug.gdocs.object.GDocsFile;

/**
 * @author a011228
 * @version
 */
public class GDocsService extends Observable {
	/**
	 * Automatically generated variable: BUFFER_SIZE
	 */
	private static final int BUFFER_SIZE = 1024;

	private DocsService mDocsService = null;

	private int mNb;

	private URL mDocumentListFeedUrl;

	/**
	 */
	public static final String DOCUMENT_CATEGORY = ConstantesService
			.getInstance().getValue("DOCUMENT_CATEGORY");

	/**
	 */
	public static final String FEED_URL_PATH = ConstantesService.getInstance()
			.getValue("FEED_URL_PATH");

	/**
	 */
	public static final String FOLDER_URL_PATH = ConstantesService
			.getInstance().getValue("FOLDER_URL_PATH");

	/**
	 * @throws java.io.IOException
	 * @throws com.google.gdata.util.ServiceException
	 * @return
	 */
	public GDocsFile getTree() throws IOException, ServiceException {
		DocumentListFeed lFeed = mDocsService.getFeed(new URL(
				mDocumentListFeedUrl.toString()), DocumentListFeed.class);
		return buildTree(lFeed);
	}

	/**
	 * @throws java.io.IOException
	 * @throws com.google.gdata.util.ServiceException
	 * @return
	 */
	public List<DocumentListEntry> getEntries() throws IOException,
			ServiceException {
		return mDocsService.getFeed(new URL(mDocumentListFeedUrl.toString()),
				DocumentListFeed.class).getEntries();

	}

	private GDocsFile buildTree(DocumentListFeed pFeed) {
		List<GDocsFile> lList = new ArrayList<GDocsFile>();
		Map<String, List<GDocsFile>> lAssoDirectoryParentDirectory = new HashMap<String, List<GDocsFile>>();
		List<GDocsFile> lListChild = null;
		String lFolderParent = null;

		for (DocumentListEntry lDocumentListEntry : pFeed.getEntries()) {
			if (lDocumentListEntry.getFolders().size() == 0) {
				LogService.debug(lDocumentListEntry.getFolders()
						+ "Root Directory"
						+ lDocumentListEntry.getTitle().getPlainText(),
						GDocsService.class);
				lList.add(new GDocsFile(lDocumentListEntry));
			} else {
				// Init parent folder

				for (String lFolder : lDocumentListEntry.getFolders()) {
					lFolderParent = lFolder;
				}

				if (lAssoDirectoryParentDirectory.containsKey(lFolderParent)) {
					lListChild = lAssoDirectoryParentDirectory
							.get(lFolderParent);
				} else {
					lListChild = new ArrayList<GDocsFile>();
				}
				lListChild.add(new GDocsFile(lDocumentListEntry));
				lAssoDirectoryParentDirectory.put(lFolderParent, lListChild);
			}

		}
		return buildNodes(lList, lAssoDirectoryParentDirectory);

	}

	private GDocsFile buildNodes(List<GDocsFile> pList,
			Map<String, List<GDocsFile>> pAssoDirectoryParentDirectory) {
		GDocsFile lDirectory = new GDocsFile(pList);
		List<GDocsFile> lList = null;
		for (GDocsFile lDocsFile : pList) {
			if (pAssoDirectoryParentDirectory.containsKey(lDocsFile.getName())) {
				lList = pAssoDirectoryParentDirectory.get(lDocsFile.getName());
				buildNodes(lList, pAssoDirectoryParentDirectory);
				lDocsFile.setListGDocs(lList);
			}
		}
		return lDirectory;
	}

	/**
	 * @return
	 */
	public final DocsService getDocsService() {
		return mDocsService;
	}

	/**
	 * @param pUsername
	 * @param pPassword
	 * @param pObserver
	 * @throws com.google.gdata.util.AuthenticationException
	 * @throws java.net.MalformedURLException
	 * @return
	 */
	public GDocsService(String pUsername, String pPassword, Observer pObserver)
			throws AuthenticationException, MalformedURLException {
		super();
		addObserver(pObserver);
		mDocumentListFeedUrl = new URL("http://"
				+ ConstantesService.getInstance().getValue("SERVER_GOOGLE")
				+ FEED_URL_PATH);

		mDocsService = new DocsService("GDocs Sync");
		login(pUsername, pPassword);
	}

	/**
	 * @param username
	 * @param password
	 * @throws com.google.gdata.util.AuthenticationException
	 */
	public void login(String username, String password)
			throws AuthenticationException {
		mDocsService.setUserCredentials(username, password);

	}

	/**
	 * @param pDocsFile
	 * @param pDirectory
	 * @param pI
	 */
	public void postChanges(GDocsFile pDocsFile, FolderEntry pDirectory, int pI) {
		FolderEntry lDirectory = null;
		if (pDocsFile.isDirectory()) {
			if (GDocsFile.DiffType.NOT_EXIST_SERVER.equals(pDocsFile
					.getDiffType())) {
				try {
					lDirectory = mkdir(pDocsFile, pDirectory);
				} catch (IOException lException) {
					LogService.showError("error.fileOpen", GDocsService.class,
							lException);
				} catch (ServiceException lException) {
					LogService.error("error.googleServiceError",
							GDocsService.class, lException);
				}
			} else {
				lDirectory = new FolderEntry();
				lDirectory.setId(pDocsFile.getEntryServer().getId());
			}
		}
		if (pDocsFile.getListGDocs() != null) {
			for (GDocsFile lDocsFile : pDocsFile.getListGDocs()) {
				if (lDocsFile.isDirectory()) {
					postChanges(lDocsFile, lDirectory, pI);
				} else {
					DocumentListEntry lDocumentListEntry = null;
					try {

						if (GDocsFile.DiffType.EXIST.equals(lDocsFile
								.getDiffType())) {
							LogService.info("No Action for - "
									+ lDocsFile.getPath() + " -- "
									+ lDocsFile.getName(), GDocsService.class);
						} else if (GDocsFile.DiffType.NOT_EXIST_SERVER
								.equals(lDocsFile.getDiffType())) {
							lDocumentListEntry = saveServerFile(lDocsFile,
									lDirectory);
						} else if (GDocsFile.DiffType.UPDATE_SERVER
								.equals(lDocsFile.getDiffType())) {
							lDocumentListEntry = updateServerFile(lDocsFile,
									lDirectory);
						} else if (GDocsFile.DiffType.UPDATE_LOCAL
								.equals(lDocsFile.getDiffType())) {
							updateLocalFile(lDocsFile, lDirectory);
						} else if (GDocsFile.DiffType.DELETE_LOCAL
								.equals(lDocsFile.getDiffType())) {
							deleteLocalFile(lDocsFile, lDirectory);
						} else if (GDocsFile.DiffType.DELETE_SERVER
								.equals(lDocsFile.getDiffType())) {
							deleteServerFile(lDocsFile, lDirectory);
						} else if (GDocsFile.DiffType.NOT_EXIST_LOCAL
								.equals(lDocsFile.getDiffType())) {
							saveLocalFile(lDocsFile, lDirectory);
						}
					} catch (InvalidEntryException lException) {
						LogService.error("error.googleServiceError",
								GDocsService.class, lException);

					} catch (IOException lException) {
						LogService.showError("error.fileOpen",
								GDocsService.class, lException);

					} catch (ServiceException lException) {
						LogService.error("error.googleServiceError",
								GDocsService.class, lException);

					}
					this.setChanged();
					mNb++;
					this.notifyObservers(mNb + "/" + pI);
				}

			}
		}
	}

	private static void updateLocalFile(GDocsFile pDocsFile,
			FolderEntry pDirectory) {
		// TODO Auto-generated method stub

	}

	private static void saveLocalFile(GDocsFile pDocsFile,
			FolderEntry pDirectory) {
		// TODO Auto-generated method stub

	}

	private static void deleteLocalFile(GDocsFile pDocsFile,
			FolderEntry pDirectory) {
		// TODO Auto-generated method stub

	}

	private static void deleteServerFile(GDocsFile pDocsFile,
			DocumentListEntry pDirectory) throws IOException, ServiceException {
		LogService.info("Delete - "
				+ ToStringBuilder.reflectionToString(pDocsFile),
				GDocsService.class);
		pDocsFile.getEntryServer().delete();
	}

	private static DocumentListEntry updateServerFile(GDocsFile pDocsFile,
			FolderEntry pDirectory) throws IOException, ServiceException {
		LogService.info("Update - "
				+ ToStringBuilder.reflectionToString(pDocsFile),
				GDocsService.class);

		File lDocumentFile = new File(pDocsFile.getAbsolutePath());
		pDocsFile.getEntryServer().setFile(
				lDocumentFile,
				getMediaTypeFromPath(lDocumentFile.getAbsolutePath())
						.getMimeType());
		pDocsFile.getEntryServer().setUpdated(
				new DateTime(new Date(lDocumentFile.lastModified()), TimeZone
						.getDefault()));
		pDocsFile.getEntryServer().updateMedia(true);

		return pDocsFile.getEntryServer();
	}

	private DocumentListEntry saveServerFile(GDocsFile pDocsFile,
			FolderEntry pDirectory) throws IOException, ServiceException {

		LogService.info("Save - "
				+ ToStringBuilder.reflectionToString(pDocsFile),
				GDocsService.class);

		DocumentListEntry lDocumentListEntry = new DocumentListEntry();
		File lDocumentFile = new File(pDocsFile.getAbsolutePath());
		lDocumentListEntry.setFile(lDocumentFile, getMediaTypeFromPath(
				lDocumentFile.getAbsolutePath()).getMimeType());
		lDocumentListEntry.setTitle(TextConstruct.create(
				TextConstruct.Type.TEXT, pDocsFile.getName(), null));
		lDocumentListEntry.setUpdated(new DateTime(pDocsFile.getLastModified(),
				TimeZone.getDefault()));
		lDocumentListEntry = mDocsService.insert(getUrl(pDirectory),
				lDocumentListEntry);

		return lDocumentListEntry;

	}

	private static URL getUrl(FolderEntry pDirectory)
			throws MalformedURLException {
		URL lUrl = null;
		if (pDirectory == null) {
			lUrl = new URL("http://"
					+ ConstantesService.getInstance().getValue("SERVER_GOOGLE")
					+ FEED_URL_PATH);
		} else {
			lUrl = new URL("http://"
					+ ConstantesService.getInstance().getValue("SERVER_GOOGLE")
					+ FOLDER_URL_PATH
					+ StringUtils.substring(pDirectory.getId(), StringUtils
							.lastIndexOf(pDirectory.getId(), "folder%3A")));
		}
		return lUrl;
	}

	private FolderEntry mkdir(GDocsFile pDocsFile, FolderEntry pDirectory)
			throws IOException, ServiceException {

		FolderEntry lFolderEntry = new FolderEntry();
		lFolderEntry.setTitle(new PlainTextConstruct(pDocsFile.getName()));

		lFolderEntry = mDocsService.insert(getUrl(pDirectory), lFolderEntry);
		if (LogService.isDebugEnabled(GDocsService.class)) {
			ToStringBuilder lBuilder = new ToStringBuilder("MkDir").append(
					"Path", pDocsFile.getPath()).append("DirName",
					lFolderEntry.getTitle().getPlainText()).append("Id",
					lFolderEntry.getId());
			if (pDirectory != null) {
				lBuilder.append("ParentId", getUrl(pDirectory)).append(
						"RealParentId", pDirectory.getId()).toString();
			}
			LogService
					.debug("Mkdir " + lBuilder.toString(), GDocsService.class);
		}
		return lFolderEntry;

	}

	/**
	 * @param pPath
	 * @return
	 */
	public static MediaType getMediaTypeFromPath(String pPath) {
		return MediaType.valueOf(StringUtils.substring(pPath,
				StringUtils.lastIndexOf(pPath, ".") + 1).toUpperCase());

	}

	/**
	 * @param pFile
	 * @return
	 */
	public static boolean isFileValid(File pFile) {
		boolean lFileValid = false;
		try {
			// PDF is not a valid type for Google Docs
			if (!MediaType.PDF.getMimeType()
					.equals(
							getMediaTypeFromPath(pFile.getAbsolutePath())
									.getMimeType())) {
				lFileValid = true;
			}
		} catch (IllegalArgumentException lException) {
			LogService.info("File " + pFile.getAbsolutePath()
					+ " not valid for GDocs Service", GDocsService.class);
			lFileValid = false;
		}
		if (pFile.isHidden()) {
			LogService.info("File hidden " + pFile.getAbsolutePath(),
					GDocsService.class);
			lFileValid = false;
		}
		return lFileValid;
	}

	private static URI getUriForEntry(DocumentListEntry pDocumentListEntry)
			throws URISyntaxException {
		String lId = pDocumentListEntry.getId().split("%3A")[1];
		String lType = pDocumentListEntry.getId().split("%3A")[0];
		lType = lType.substring(lType.lastIndexOf("/") + 1);
		String lEntryLink = pDocumentListEntry.getDocumentLink().getHref();
		String lUriStr = lEntryLink.substring(0,
				lEntryLink.lastIndexOf("/") + 1).replace("http:", "https:");
		if ("document".equals(lType)) {
			lUriStr += "MiscCommands?command=saveasdoc&docID=" + lId
					+ "&exportFormat=doc";
		} else if ("spreadsheet".equals(lType)) {
			lUriStr += "ccc?key=" + lId + "&hl=en";
		} else if ("presentation".equals(lType)) {
			lUriStr += "MiscCommands?command=saveasdoc&docID=" + lId
					+ "&exportFormat=ppt";
		}
		return new URI(lUriStr);
	}

	/**
	 * @param pDocumentListEntry
	 * @throws java.io.IOException
	 * @throws java.net.URISyntaxException
	 * @return
	 */
	public byte[] fetchDocument(DocumentListEntry pDocumentListEntry)
			throws IOException, URISyntaxException {
		URI lUri = getUriForEntry(pDocumentListEntry);
		HttpURLConnection.setDefaultAllowUserInteraction(true);
		HttpURLConnection lConnection = (HttpURLConnection) new URL(lUri
				.toString()).openConnection();
		HttpAuthToken authToken = (HttpAuthToken) this.getDocsService()
				.getAuthTokenFactory().getAuthToken();
		lConnection.setRequestProperty("Authorization", authToken
				.getAuthorizationHeader(lConnection.getURL(), "GET"));
		lConnection.setRequestProperty("User-Agent", "Mozilla/5.0");
		lConnection.setRequestMethod("GET");
		lConnection.connect();
		InputStream lInputStream = null;
		ByteArrayOutputStream lArrayOutputStream = null;
		try {
			lInputStream = lConnection.getInputStream();
			lArrayOutputStream = new ByteArrayOutputStream();
			byte[] buffer = new byte[BUFFER_SIZE];
			int lNbLecture = 0;
			while ((lNbLecture = lInputStream.read(buffer)) != -1) {
				lArrayOutputStream.write(buffer, 0, lNbLecture);
			}
			return lArrayOutputStream.toByteArray();
		} finally {
			if (lInputStream != null) {
				lInputStream.close();
			}
			if (lArrayOutputStream != null) {
				lArrayOutputStream.close();
			}
		}
	}

}
